#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Threading;
using BHG.System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Media;
using FarseerPhysics.Collision;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics.Joints;

using GameCamera;
#endregion

namespace GameStateManagement {
    /// <summary>
    /// This screen implements the actual game logic. It is just a
    /// placeholder to get the idea across: you'll probably want to
    /// put some more interesting gameplay in here!
    /// </summary>
    class GameplayScreen : GameScreen {
        protected bool gravityGunAllowed = false;
        protected bool blackHoleGunAllowed = false;

        protected const float CAMERA_OFFSET_X = 0.0f;         // camera x offset
        protected const float CAMERA_OFFSET_Y = 0.0f;         // camera y offset
        protected const float CAMERA_SPEED = 0.3f;            // camera movement speed in free mode
        protected const float TIME_TO_REFRESH_BLACKHOLEGUN = 5f;

        protected const float GRAVITYGUN_FORCE_MULTIPLIER = 100.0f;               // Maximum force for gravity gun ( multiplier * body mass )
        protected readonly Vector2 MOUSE_POINT_DELTA = new Vector2( 0.001f, 0.001f );        // Gravity Gun delta for detection of clicked object
        protected readonly Vector2 DEFAULT_WORLD_GRAVITY = new Vector2( 0.0f, -20.0f );

        #region Fields
        protected ContentManager content;
        protected float pauseAlpha;
        protected Texture2D cursorTexture, mouseLeft, mouseRight;
        protected Vector2 curMousePosition = new Vector2( 0, 0 );

        //sounds
        SoundEffect step1, step2, missilelaunch, fastBlackHole, jump;

        // camera
        protected Camera camera;
        protected bool cameraMove = false;

        // models for player and BH
        protected Model playerModel;
        protected GamePlayer player;
        protected Model BlackHoleModel;
        protected BlackHoleGunObject BlackHole;
        protected float blackHoleWeaponTime;

        protected Model bottomModel, topModel, leftModel, rightModel, infoBarModel;
        protected GraphicObject bottom, top, left, right;
        protected GraphicObject[] infoBarTimer;


        // Gravity Gun
        protected bool GravityGunHeld = false;      /* GG gun holding object flag */
        protected FixedMouseJoint GravityGunJoint = null;

        // list of object on map to be draw
        protected List<AbstractObject> objectsOnMap = new List<AbstractObject>( );
        #endregion

        protected bool PlaySteps = true;

        #region Initialization
        /// <summary>
        /// Constructor
        /// </summary>
        public GameplayScreen( ) {
            TransitionOnTime = TimeSpan.FromSeconds( 1.5 );
            TransitionOffTime = TimeSpan.FromSeconds( 0.5 );
        }

        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent( ) {
            objectsOnMap = new List<AbstractObject>( );
            PhysicsEngine.Instance.Reset( );
            if( content == null ) content = new ContentManager( ScreenManager.Game.Services, "Content" );
            /* set initial gravity */
            PhysicsEngine.Instance.Gravity = DEFAULT_WORLD_GRAVITY;

            /* player */
            playerModel = content.Load<Model>( "Models/Player/dude" );
            player = new GamePlayer( new Vector2( 2.0f, 6.0f ), playerModel );
            player.Dislocation = new Vector3( 0f, -4.5f, 0f );
            objectsOnMap.Add( player );

            /* sound */
            step1 = content.Load<SoundEffect>( "Sound/sounds/step1" );
            step2 = content.Load<SoundEffect>( "Sound/sounds/step2" );
            missilelaunch = content.Load<SoundEffect>( "Sound/sounds/missilelaunch" );
            fastBlackHole = content.Load<SoundEffect>( "Sound/sounds/fastBlackHole" );
            jump = content.Load<SoundEffect>( "Sound/sounds/jump" );

            /* models */
            BlackHoleModel = content.Load<Model>( "Models/Blackhole/black-hole" );

            bottomModel = content.Load<Model>( "Models/Infobars/black-hole-gun-info/bottom" );
            topModel = content.Load<Model>( "Models/Infobars/black-hole-gun-info/top" );
            leftModel = content.Load<Model>( "Models/Infobars/black-hole-gun-info/left" );
            rightModel = content.Load<Model>( "Models/Infobars/black-hole-gun-info/right" );
            infoBarModel = content.Load<Model>( "Models/Infobars/black-hole-gun-info/middle-indicator" );

            /* set initial camera */
            camera = new Camera(
                new Vector3( player.Position.X - CAMERA_OFFSET_X, player.Position.Y - CAMERA_OFFSET_Y, 0.0f ),                          /* look at */
                60.0f,                                                  /* distance */
                0.0f,                                                   /* yaw */
                MathHelper.ToRadians( -20.0f ),                            /* pitch */
                0.0f,                                                   /* roll */
                ScreenManager.GraphicsDevice.Viewport.AspectRatio,      /* aspect ratio */
                1f,                                                 /* scale */
                MathHelper.ToRadians( 45.0f ),                            /* FOV */
                1.0f,                                                   /* near plane */
                10000.0f );                                              /* far plane */

            /* cursor */
            cursorTexture = content.Load<Texture2D>( "ScreenManager/crosshair" );
            mouseLeft = content.Load<Texture2D>("ScreenManager/mouse_left");
            mouseRight = content.Load<Texture2D>("ScreenManager/mouse_right");
            ScreenManager.Game.ResetElapsedTime();
        }

        public void CreateShowInfobar( ) {
            if( top == null ) {
                top = new GraphicObject( new Vector2( -3f, 25.487f ), bottomModel, 45f );
                top.Scale = 0.1f;
                objectsOnMap.Add( top );

                bottom = new GraphicObject( new Vector2( -3f, 25.055f ), bottomModel, 45f );
                bottom.Scale = 0.1f;
                objectsOnMap.Add( bottom );

                left = new GraphicObject( new Vector2( -4.25f, 25.27f ), leftModel, 45 );
                left.Scale = 0.1f;
                objectsOnMap.Add( left );

                right = new GraphicObject( new Vector2( -1.75f, 25.27f ), leftModel, 45 );
                right.Scale = 0.1f;
                right.RotationZ = 1.6f * 2;
                objectsOnMap.Add( right );

                infoBarTimer = new GraphicObject[45];
                for( int i = 0; i < infoBarTimer.Length; i++ ) {
                    infoBarTimer[i] = new GraphicObject( new Vector2( -4.1f, 25.27f ), infoBarModel, 45 );
                    infoBarTimer[i].Scale = 0.05f;
                    objectsOnMap.Add( infoBarTimer[i] );
                }
            }
        }

        public void UpdateShowInfobar( float x, float y ) {
            if( top != null ) {
                x -= 2.5f;
                y -= 5;
                top.Position = new Vector2( x - 3f, y + 25.487f );
                bottom.Position = new Vector2( x - 3f, y + 25.055f );
                left.Position = new Vector2( x - 4.25f, y + 25.27f );
                right.Position = new Vector2( x - 1.75f, y + 25.27f );
                int howMuchShow = HowMuchShow( infoBarTimer.Length, blackHoleWeaponTime, TIME_TO_REFRESH_BLACKHOLEGUN );
                for( int i = 0; i < infoBarTimer.Length; i++ ) {
                    infoBarTimer[i].Position = new Vector2( x - 4.1f + i*0.05f, y + 25.27f );
                    if( i > howMuchShow ) {
                        infoBarTimer[i].Hide = true;
                    } else {
                        infoBarTimer[i].Hide = false;
                    }
                }
            }
        }

        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent( ) {
            // reset physics engine
            PhysicsEngine.Instance.Reset( );
            content.Unload( );
        }
        #endregion

        public int HowMuchShow( int count, float actual, float maximum ) {
            if( actual < 0 ) {
                return count;
            }
            return (int) ((1 - (actual / maximum)) * count);
        }

        #region Update
        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update( GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen ) {
            base.Update( gameTime, otherScreenHasFocus, false );

            blackHoleWeaponTime -= (float) gameTime.ElapsedGameTime.TotalSeconds;

            UpdateShowInfobar( player.Position.X, player.Position.Y );

            if( player.DoJump( ) ) {
                jump.Play( 0.3f, 0, 0 );
            }

            if( (player.stepSound( gameTime )) && (PlaySteps) ) {
                Random random = new Random( );
                int chance = random.Next( 2 );
                if( chance == 0 ) {
                    step1.Play( player.strengOfStep( ), 0, 0 );
                } else {
                    step2.Play( player.strengOfStep( ), 0, 0 );
                }
            }

            // Gradually fade in or out depending on whether we are covered by the pause screen.
            if( coveredByOtherScreen )
                pauseAlpha = Math.Min( pauseAlpha + 1f / 32, 1 );
            else
                pauseAlpha = Math.Max( pauseAlpha - 1f / 32, 0 );

            /* if active, update game physics */
            if( IsActive ) {
                PhysicsEngine.Instance.Update( (float) gameTime.ElapsedGameTime.TotalMilliseconds * 0.001f );

                for( int i = 0; i < objectsOnMap.Count; i++ ) {
                    objectsOnMap[i].Update( gameTime );
                    if( objectsOnMap[i].state == State.REMOVED ) {
                        removeObject( i );
                        i--;
                    }
                }

                if( BlackHole != null && BlackHole.state == State.REMOVED ) BlackHole = null;
            }
        }
        #endregion

        #region HandleInput
        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput( InputState input, GameTime gameTime ) {
            if( input == null ) throw new ArgumentNullException( "input" );

            // Look up inputs for the active player profile.
            int playerIndex = (int) ControllingPlayer.Value;
            KeyboardState kState = input.CurrentKeyboardStates[playerIndex];
            KeyboardState prevKeyboard = input.LastKeyboardStates[playerIndex];

            if( input.IsPauseGame( ControllingPlayer ) ) {
                ScreenManager.AddScreen( new PauseMenuScreen( ), ControllingPlayer );
            } else {
#if DEBUG
                if( cameraMove ) {
                    if( (kState.IsKeyDown( Keys.M ) && (prevKeyboard.IsKeyDown( Keys.M ) == false)) ) cameraMove = false;

                    /* camera control, unnececary */
                    if (kState.IsKeyDown(Keys.Left)) camera.moveCamera(1 * CAMERA_SPEED, 0.0f, 0.0f);
                    if (kState.IsKeyDown(Keys.Right)) camera.moveCamera(-1 * CAMERA_SPEED, 0.0f, 0.0f);
                    if (kState.IsKeyDown(Keys.Down)) camera.moveCamera(0.0f, 1 * CAMERA_SPEED, 0.0f);
                    if (kState.IsKeyDown(Keys.Up)) camera.moveCamera(0.0f, -1 * CAMERA_SPEED, 0.0f);
                    if (kState.IsKeyDown(Keys.NumPad1)) camera.moveCamera(0.0f, 0.0f, 1 * CAMERA_SPEED);
                    if (kState.IsKeyDown(Keys.NumPad3)) camera.moveCamera(0.0f, 0.0f, -1 * CAMERA_SPEED);
                    if (kState.IsKeyDown(Keys.NumPad4)) camera.rotateAroundlookAt(MathHelper.ToRadians(10 * CAMERA_SPEED), 0.0f);
                    if (kState.IsKeyDown(Keys.NumPad6)) camera.rotateAroundlookAt(MathHelper.ToRadians(-10 * CAMERA_SPEED), 0.0f);
                    if (kState.IsKeyDown(Keys.NumPad8)) camera.rotateAroundlookAt(0.0f, MathHelper.ToRadians(10 * CAMERA_SPEED));
                    if (kState.IsKeyDown(Keys.NumPad2)) camera.rotateAroundlookAt(0.0f, MathHelper.ToRadians(-10 * CAMERA_SPEED));
                    if (kState.IsKeyDown(Keys.NumPad7)) camera.rollCamera(MathHelper.ToRadians(-10 * CAMERA_SPEED));
                    if (kState.IsKeyDown(Keys.NumPad9)) camera.rollCamera(MathHelper.ToRadians(10 * CAMERA_SPEED));
                    if (kState.IsKeyDown(Keys.W)) camera.zoom(1.0f * CAMERA_SPEED);
                    if (kState.IsKeyDown(Keys.S)) camera.zoom(-1.0f * CAMERA_SPEED);
                    if (kState.IsKeyDown(Keys.R)) camera.resetYawPitchRollDistance();
                    /**/
                } else {
                    if( (kState.IsKeyDown( Keys.M ) && (prevKeyboard.IsKeyDown( Keys.M ) == false)) ) cameraMove = true;

#endif
                    player.HandleInput( input, (int) ControllingPlayer.Value );
                    camera.lookAt = new Vector3( player.Position.X - CAMERA_OFFSET_X, player.Position.Y - CAMERA_OFFSET_Y, 0.0f );

                    HandleMouseInput( input, gameTime );
#if DEBUG
                }
#endif
            }
        }

        protected void HandleMouseInput( InputState input, GameTime gameTime ) {
            MouseState current = input.CurrentMouseState;
            MouseState last = input.LastMouseState;

            this.curMousePosition.X = current.X - cursorTexture.Width/2;
            this.curMousePosition.Y = current.Y - cursorTexture.Height/2;


            if( current.RightButton == ButtonState.Pressed && last.RightButton == ButtonState.Released && blackHoleGunAllowed ) {
                if( blackHoleWeaponTime < 0 ) {
                    Vector3 mousePos = getMouse3D( input );
                    HandleBlackHoleGun( new Vector2( mousePos.X, mousePos.Y ), gameTime );
                    missilelaunch.Play( );
                    fastBlackHole.Play( );
                    blackHoleWeaponTime = TIME_TO_REFRESH_BLACKHOLEGUN;
                }
            } else if( current.LeftButton == ButtonState.Pressed && last.LeftButton == ButtonState.Released && gravityGunAllowed ) {
                Vector3 mousePos = getMouse3D( input );
                GravityGunAdd( new Vector2( mousePos.X, mousePos.Y ) );
            } else if( current.LeftButton == ButtonState.Released && last.LeftButton == ButtonState.Pressed && gravityGunAllowed ) {
                GravityGunRemove( );
            } else if( GravityGunHeld == true && gravityGunAllowed ) {
                Vector3 mousePos = getMouse3D( input );
                GravityGunMove( new Vector2( mousePos.X, mousePos.Y ) );
            }
        }

        protected Vector3 getMouse3D( InputState input ) {
            // far point and near point in worldspace
            Vector3 mousePosFar = ScreenManager.GraphicsDevice.Viewport.Unproject( new Vector3( input.CurrentMouseState.X, input.CurrentMouseState.Y, 1.0f ), camera.projectionMatrix, camera.viewMatrix, Matrix.Identity );
            Vector3 mousePosNear = ScreenManager.GraphicsDevice.Viewport.Unproject( new Vector3( input.CurrentMouseState.X, input.CurrentMouseState.Y, 0.0f ), camera.projectionMatrix, camera.viewMatrix, Matrix.Identity );
            // direction
            Vector3 direction = mousePosFar - mousePosNear;
            direction.Normalize( );

            // ray and plane intersect
            Ray r = new Ray( mousePosNear, direction );
            Vector3 n = new Vector3( 0f, 0f, 1f );
            Plane p = new Plane( n, 0f );

            float denominator = Vector3.Dot( p.Normal, r.Direction );
            float numerator = Vector3.Dot( p.Normal, r.Position ) + p.D;
            float t = -(numerator / denominator);

            return (mousePosNear + direction * t);
        }

        #endregion

        #region Draw
        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw( GameTime gameTime ) {
            /* background */
            ScreenManager.GraphicsDevice.Clear( Color.Black );
            
            /* spriteBatch dit set this value but did not unset */
            ScreenManager.GraphicsDevice.BlendState = BlendState.Opaque;
            ScreenManager.GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            /* draw object list */
            foreach( AbstractObject obj in objectsOnMap ) {
                if( obj is GamePlayer ) DrawPlayer( (GamePlayer) obj );
                else
                    if( obj.state != State.REMOVED ) DrawGameObject( obj );
            }

            // If the game is transitioning on or off, fade it out to black.
            if( TransitionPosition > 0 || pauseAlpha > 0 ) {
                float alpha = MathHelper.Lerp( 1f - TransitionAlpha, 1f, pauseAlpha / 2 );

                ScreenManager.FadeBackBufferToBlack( alpha );
            }
            
            ///* Draw Cursor */
            ScreenManager.SpriteBatch.Begin( );
            if (blackHoleGunAllowed || gravityGunAllowed)
            {
                ScreenManager.SpriteBatch.Draw(cursorTexture, this.curMousePosition, Color.White);
            }            
            if (blackHoleGunAllowed)
            {
                ScreenManager.SpriteBatch.Draw(mouseRight, new Vector2(ScreenManager.GraphicsDevice.Viewport.Width*0.11f, ScreenManager.GraphicsDevice.Viewport.Height * 0.13f), Color.White);
            }
            if (gravityGunAllowed)
            {
                ScreenManager.SpriteBatch.Draw(mouseLeft, new Vector2(ScreenManager.GraphicsDevice.Viewport.Width * 0.07f, ScreenManager.GraphicsDevice.Viewport.Height * 0.13f), Color.White);
            }
            ScreenManager.SpriteBatch.End();
        }

        /// <summary>
        /// Draws game object
        /// </summary>
        /// <param name="obj">Object to be drawn</param>
        void DrawGameObject( AbstractObject obj ) {
            // Do not draw objects outside of screen, does not include look distance
            //if ( ( Math.Abs(obj.Position.X - camera.lookAt.X) > 60 ) || ( Math.Abs(obj.Position.Y - camera.lookAt.Y) > 60 ) ) {
            //    return;
            //}
            if( obj.Hide ) {
                return;
            }
            Matrix[] transforms = new Matrix[obj.Model.Bones.Count];
            obj.Model.CopyAbsoluteBoneTransformsTo( transforms );

            foreach( ModelMesh mesh in obj.Model.Meshes ) {
                foreach( BasicEffect effect in mesh.Effects ) {

                    effect.EnableDefaultLighting( );
                    effect.World = transforms[mesh.ParentBone.Index] *
                                    Matrix.CreateScale( camera.Scale*obj.Scale ) *
                                    Matrix.CreateFromYawPitchRoll( obj.RotationZ, obj.RotationY, obj.Rotation ) *
                                    Matrix.CreateTranslation( new Vector3( obj.Position.X + obj.Dislocation.X, obj.Position.Y + obj.Dislocation.Y, obj.DepthValue( ) + obj.Dislocation.Z ) );
                    effect.View = camera.viewMatrix;
                    effect.Projection = camera.projectionMatrix;
                }
                mesh.Draw( );
            }
        }

        void DrawPlayer( GamePlayer pl ) {
            // Compute camera matrices.
            Matrix view = camera.viewMatrix;
            Matrix projection = camera.projectionMatrix;
            Matrix[] bones = pl.AnimationPlayer.GetSkinTransforms( );

            // Render the skinned mesh.
            foreach( ModelMesh mesh in pl.Model.Meshes ) {
                foreach( SkinnedEffect effect in mesh.Effects ) {

                    effect.World = Matrix.CreateScale( camera.Scale*pl.Scale ) *
                                    Matrix.CreateFromYawPitchRoll( pl.RotationZ, pl.RotationY, pl.Rotation ) *
                                    Matrix.CreateTranslation( new Vector3( pl.Position.X+pl.Dislocation.X, pl.Position.Y+pl.Dislocation.Y, pl.DepthValue( )+pl.Dislocation.Z ) );
                    effect.SetBoneTransforms( bones );
                    effect.View = view;
                    effect.Projection = projection;

                    effect.EnableDefaultLighting( );

                    effect.SpecularColor = new Vector3( 0.25f );
                    effect.SpecularPower = 16;
                }

                mesh.Draw( );
            }
        }

        #endregion

        public void removeObject( int index ) {
            //if (objectsOnMap[index] is BlackHoleGunObject)
            //{
            //    player.moveALittleUp();
            //    player.Contacts = 0;
            //}
            objectsOnMap[index].doRemove( );
            objectsOnMap.RemoveAt( index );
        }

        #region Weapons
        protected void HandleBlackHoleGun( Vector2 mousePosition, GameTime gameTime ) {
            if( BlackHole != null ) {
                objectsOnMap.Remove( BlackHole );
                BlackHole.doRemove( );
                BlackHole = null;
            }

            BlackHole = new BlackHoleGunObject( mousePosition, BlackHoleModel, gameTime );
            BlackHole.body.FixtureList[0].OnCollision += onBlackHoleCollision;
            objectsOnMap.Add( BlackHole );
        }

        protected void GravityGunAdd( Vector2 mousePos ) {
            if( GravityGunHeld == true ) return;

            AABB MousePoint;
            MousePoint.LowerBound = mousePos - MOUSE_POINT_DELTA;
            MousePoint.UpperBound = mousePos + MOUSE_POINT_DELTA;

            Fixture ClickedObject = null;

            // Query the world for overlapping shapes.
            PhysicsEngine.Instance.gameWorld.QueryAABB(
                fixture => {
                    Body body = fixture.Fixture.Body;
                    if( body.BodyType == BodyType.Dynamic ) {
                        bool inside = fixture.Fixture.TestPoint( ref mousePos );
                        if( inside ) {
                            ClickedObject = fixture.Fixture;

                            // We are done, terminate the query.
                            return false;
                        }
                    }

                    // Continue the query.
                    return true;
                }, ref MousePoint );

            if( ClickedObject != null && !ClickedObject.Body.UserData.Equals( player ) ) {
                Body body = ClickedObject.Body;
                GravityGunJoint = new FixedMouseJoint( body, mousePos );
                GravityGunJoint.MaxForce = GRAVITYGUN_FORCE_MULTIPLIER * body.Mass;
                PhysicsEngine.Instance.gameWorld.AddJoint( GravityGunJoint );
                body.Awake = true;

                GravityGunHeld = true;
            }
        }
        protected void GravityGunRemove( ) {
            if( GravityGunHeld == false ) return;

            PhysicsEngine.Instance.gameWorld.RemoveJoint( GravityGunJoint );
            GravityGunJoint = null;

            GravityGunHeld = false;
        }
        protected void GravityGunMove( Vector2 mousePos ) {
            GravityGunJoint.Target = mousePos;
        }
        #endregion

        protected bool onBlackHoleCollision( Fixture fixtureA, Fixture fixtureB, Contact manifold ) {
            BlackHole.KillBlackHole( );
            return true;
        }
    }
}
